package starter;

import java.util.LinkedList;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

import org.omg.CORBA.ORB;
import org.omg.PortableServer.POA;
import org.omg.PortableServer.POAHelper;

import monitor.Monitor;
import ggt.Koordinator;
import ggt.Prozess;
import ggt.ProzessHelper;
import ggt.ProzessPOA;
import ggt.KoordinatorPackage.exAlreadyExists;

public class ProzessImpl extends ProzessPOA implements Runnable{
	String name;
	private int mi, delay, timeout;
	private Prozess rechterNachbar, linkerNachbar;
	private Monitor monitor;
	private Koordinator koor;
	private Prozess ich;
	
	private LinkedList<Integer> zahlen;
	private LinkedList<Prozess> terminatoren;
	private int status, erstellt = 1, bereit = 2, rechnen = 3, terminieren = 4, beendet = 5, beenden = 6;
	private long timeLastMessage;
	private Semaphore warten;
	private ReentrantLock lts;
	
	public ProzessImpl(String name, ORB orb, Koordinator koor){
		this.name = name;
		zahlen = new LinkedList<Integer>();
		terminatoren = new LinkedList<Prozess>();
		warten = new Semaphore(0);
		status = erstellt;
		lts = new ReentrantLock(true);
		POA rootPoa;
		try {
			rootPoa = POAHelper.narrow(orb.resolve_initial_references("RootPOA"));
			rootPoa.the_POAManager().activate();
			this.ich = ProzessHelper.narrow(rootPoa.servant_to_reference(this));
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		try {
			koor.registiereProzess(ich);
		} catch (exAlreadyExists e) {
			e.printStackTrace();
		}
	}
	
	@Override
	public String name() {
		return this.name;
	}

	@Override
	public void getZahl(int zahl, Prozess p) {
		this.zahlen.add(zahl);
		//this.timeLastMessage = System.currentTimeMillis();
		monitor.rechnen(ich.name(), p.name(), zahl);
		status(rechnen);
		this.warten.release();
	}

	@Override
	public void terminieren(Prozess prozess) {
		lts.lock();
		this.terminatoren.add(prozess);
		lts.unlock();
		this.warten.release();
	}

	@Override
	public void quit(String starterName) {
		status = beenden;
		this.warten.release();
	}

	@Override
	public void init(Prozess rechterNachbar, Prozess linkerNachbar,
			int startGGT, int delay, int timeout, Monitor theMonitor, Koordinator koor) {
		this.rechterNachbar = rechterNachbar;
		this.linkerNachbar = linkerNachbar;
		this.mi = startGGT;
		this.delay = delay;
		this.timeout = timeout;
		this.monitor = theMonitor;
		this.koor = koor;
		this.status = bereit;
		this.warten.release();
	}

	@Override
	public void starteBerechung() {
		this.rechterNachbar.getZahl(this.mi, ich);
		this.linkerNachbar.getZahl(this.mi, ich);
		status(rechnen);
		this.timeLastMessage = System.currentTimeMillis();
		this.warten.release();
	}

	@Override
	public void run() {
		long wartezeit;
		try{
			while(status < beenden){
				if(status == erstellt){
					this.warten.acquire();
				}else if(status == bereit){
					this.warten.acquire();	
					this.warten.release();
				}else if(status == rechnen){
					wartezeit = timeout - (System.currentTimeMillis() - this.timeLastMessage);
					if(wartezeit < 0 ){
						wartezeit = timeout;
					}
					this.warten.tryAcquire(wartezeit, TimeUnit.MILLISECONDS);	
					if(!terminierungsAnfrage()){
						if(!rechnen()){
							if(System.currentTimeMillis() - this.timeLastMessage >= this.timeout){
								rechterNachbar.terminieren(ich);
								status(terminieren);
							}
						}
					}					
				}else if(status == terminieren){
					this.warten.tryAcquire(timeout/2, TimeUnit.MILLISECONDS);	
					if(!terminierungsAnfrage()){
						rechterNachbar.terminieren(ich);
					}
				}else if(status == beendet){
					this.warten.acquire();
				}
			}
        } catch (Exception e) {
            System.err.println("ERROR: " + e);
            e.printStackTrace(System.out);
        }
	}
	
	@Override
	public void stop(Prozess stopper) {
		status = beendet;
		this.terminatoren.clear();
		this.warten.release();
		if(!stopper.name().equals(this.name())){
			this.rechterNachbar.stop(stopper);
		}	
	}
	
	private boolean rechnen(){
		Integer y;
		if (zahlen.size() < 1){
			return false;
		}
		
		y = zahlen.pop();
		if (y < this.mi){
			mi = (mi-1)%y +1;
			try {
				Thread.sleep(delay);
				this.timeLastMessage = System.currentTimeMillis();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			this.rechterNachbar.getZahl(this.mi, ich);
			this.linkerNachbar.getZahl(this.mi, ich);
		}
		return true;
	}
	
	private boolean terminierungsAnfrage(){
		Prozess p;
		lts.lock();
		if(terminatoren.size() < 1){
			lts.unlock();
			return false;
		}
		p = this.terminatoren.pop();
		lts.unlock();
		
		if(p.name().equals(this.name)){
			// Koordinator fragen ob Ergebnis an Monitor senden
			if(koor.terminerungAbgeschlossen(ich,mi)){
				monitor.ergebnis(name, mi);
				rechterNachbar.stop(ich);
			}
			status = beendet;
		}else if(System.currentTimeMillis() - this.timeLastMessage > this.timeout/2){
			rechterNachbar.terminieren(p);
			monitor.terminieren(name, p.name(), true);
		}else{
			monitor.terminieren(name, p.name(), false);
		}
		return true;
	}
	
	@SuppressWarnings("unused")
	private void status(int neuerStatus){
		if(status > terminieren){
			
		}else{
			status = neuerStatus;
		}
	}

}
